#!/usr/bin/env python
'''
Copyright 2013 Hashem Masoud http://code.google.com/p/pullfeed/

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''

import feedparser
import ConfigParser
import codecs
import sys
import getopt
import os
import markup
import pickle
from time import strftime
from markup import oneliner as oneliner_object

#Definition of contstants
HOME_DIR = os.path.expanduser("~")
CONFIG_FILE = HOME_DIR + '/.pullfeed'
HIST_FILE = HOME_DIR + '/.pullfeed-hist'
SCRIPT_ENCODING = 'utf-8'
HTML_CHARSET = 'UTF-8'


class PullFeed:

	config_file = CONFIG_FILE		# Default location of the config file
	feed_URL = ''  					# This will get the list of URLs from the config file
	output_file = ''  				# The output HTML file
	include_description = False
	file_object = ''
	feed_objects = []  				# List of parsed feed objects
	feed_hist = {}					# Dictionary for storing a feeds last update. 
									# It will be of the form: key (the feed URL)
									# and value (a list of feed entries (URLs))
	
	def exit_with_message(self, msg):
		"""Exit program with a meaningful message"""
		print msg
		exit(1)

	
	def read_args(self):
		"""Check command line arguments"""
		if len(sys.argv) > 2:
			self.exit_with_message('Usage: ' + str(sys.argv[0]) +
				' [Configuration filename]')
		elif len(sys.argv) == 2:
			self.config_file = str(sys.argv[1])
			
	
	def read_config(self):
		"""Function to read the configuration file"""
		
		#Check whether the file exists
		if not os.access(self.config_file, os.F_OK):
			self.exit_with_message('Error: Couldn\'t find configuration file: '
				+ self.config_file)
		
		#Check whether the file is readable 
		if not os.access(self.config_file, os.R_OK):
			self.exit_with_message('Error: Couldn\'t read configuration file: '
				+ self.config_file + ', permission denied')
		
		#Read configuration file
		config = ConfigParser.RawConfigParser()
		config.read(self.config_file)
		
		#Read configuration options
		try:
			feed = config.get('General', 'feedURL')
			self.feed_URL = feed.split()  # Convert from string to list
			self.output_file = config.get('General', 'outputfile')
			self.include_description = config.getboolean('General',
				'include_description')
			#self.dscrMaxLimit = config.getint('General', 'dscrmaxlimit')

		except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
			self.exit_with_message('Error: Configuration file is not correct: '
				+ str(sys.exc_value))

	
	def write_feeds_as_html(self):
		"""Write feeds to the file as HTML code"""
		
		self.file_object = open(self.output_file, 'w')
		
		page = markup.page()
		
		title = "Output of the " + os.path.basename(__file__) + " script"
		styles = ('layout.css')
						
		page.init(css=styles, title=title, charset=HTML_CHARSET)
		
		for feeds_iter in range(len(self.feed_objects)):
			# Add feed object headers to page
		
			try:
				page.h1(str(self.feed_objects[feeds_iter].feed.title),
					align='center')
				page.h2(str(self.feed_objects[feeds_iter].feed.description),
					align='center')
				page.h3(oneliner_object.a(
					str(self.feed_objects[feeds_iter].feed.link),
						href=str(self.feed_objects[feeds_iter].feed.link)),
						align='center')
			except (KeyError, AttributeError):
				#print str(sys.exc_type) + str(sys.exc_value)
				continue

			for entries_iter in range(len(self.feed_objects[feeds_iter].entries)):
			# Add feed entries to page
				try: 
					# Include feed entry only if it wasn't pulled earlier.
					# We use the previously stored data to search for the entry.
					self.feed_hist[str(self.feed_objects[feeds_iter].feed.title_detail['base'])].index(str(self.feed_objects[feeds_iter].entries[entries_iter].link))
				except (KeyError, ValueError):
					page.h4(oneliner_object.a(str(self.feed_objects[feeds_iter].entries[entries_iter].title), href=(str(self.feed_objects[feeds_iter].entries[entries_iter].link))))
					page.span(strftime("%a %d %B %Y %H:%M", self.feed_objects[feeds_iter].entries[entries_iter].updated_parsed), style="color:grey")
					if self.include_description and hasattr(self.feed_objects[feeds_iter].entries[entries_iter], 'description'):						
						page.blockquote(str(self.feed_objects[feeds_iter].entries[entries_iter].description))

				
		self.file_object.write(str(page))
				
		
	def populate_feed_objects(self):
		"""Function to populate the feeds object list"""
		
		for feeds_iter in range(len(self.feed_URL)):
	
			self.feed_objects.append(feedparser.parse(self.feed_URL[feeds_iter]))
			try:
				print 'Pulling: ' + str(self.feed_objects[feeds_iter].feed.title)
			except AttributeError:
				continue


	def read_feeds_hist(self):
		"""Function to unpickle from HIST_FILE into feed_hist object."""
		
		try:
			hist_file = open(HIST_FILE, 'rb')
			self.feed_hist = pickle.load(hist_file)
			hist_file.close()
		
		except:
			#self.exit_with_message('Error: The file: ' + HIST_FILE + ' Cannot be found')
			# Handle situations of: inexistent file, 
			# file exists but with no read permissions,
			# or unpickled object is not a dict
			if type(self.feed_hist) != dict:
				self.feed_hist = {}

	
	def write_feeds_hist(self):
		"""Function to update the feed_hist object and pickle it to HIST_FILE
		This is used to mark entries as read so that we don't add them to
		the output file again"""
		
		entries_list = []
		
		for feeds_iter in range(len(self.feed_objects)):
			try:
				for entries_iter in range(len(self.feed_objects[feeds_iter].entries)):
					entries_list.append(self.feed_objects[feeds_iter].entries[entries_iter].link)
					
				self.feed_hist[str(self.feed_objects[feeds_iter].feed.title_detail['base'])] = entries_list
									
			except AttributeError:
				continue
				
		try:
			hist_file = open(HIST_FILE, 'wb')
			pickle.dump(self.feed_hist, hist_file)
			hist_file.close()
		except IOError:
			# Handle situation of file with no permission to write to
			print str(sys.exc_type) + str(sys.exc_value)
			
	

def main():
	reload(sys)
	sys.setdefaultencoding(SCRIPT_ENCODING)  # Set the encoding
	
	feeds = PullFeed()
	feeds.read_args()
	feeds.read_config()
	feeds.populate_feed_objects()
	feeds.read_feeds_hist()
	feeds.write_feeds_as_html()
	feeds.write_feeds_hist()
		

if __name__ == "__main__":
	main()

